import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;

public class FileDemo {
    // 深度优先遍历
    protected static void depthFirstLoop(String filePath) {
        File dir = new File(filePath); // filePath 要遍历的目录
        //System.out.println(dir);
        if (dir.isDirectory()) {
            System.out.println("dir: "+filePath);
            // 返回
            String[] children = dir.list();
            for(String sub : children) {
                if (sub.startsWith(".")) {
                    System.out.println("skip "+sub);
                    continue;
                }
                depthFirstLoop(filePath + File.separator + sub);
            }
        } else {
            System.out.println("file: "+filePath);
        }
    }

    // 深度优先遍历 当前类工作目录
    protected static void fileSysLoop() {

        depthFirstLoop(currentWorkdingDir());

    }

    public static String currentWorkdingDir() {
        File directory = new File("");
        try {
            // 获取当前类工作目录
            String workingDIRPath = directory.getCanonicalPath();
            return workingDIRPath;
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }

    protected static void textFileIO() {
        String poem = """
水调歌头·明月几时有
苏轼
丙辰中秋，欢饮达旦，大醉，作此篇，兼怀子由。
 
明月几时有？把酒问青天。
不知天上宫阙，今夕是何年。
我欲乘风归去，又恐琼楼玉宇，高处不胜寒。
起舞弄清影，何似在人间。
 
转朱阁，低绮户，照无眠。
不应有恨，何事长向别时圆？
人有悲欢离合，月有阴晴圆缺，此事古难全。
但愿人长久，千里共婵娟。
""";

        // https://blog.csdn.net/weixin_43916418/article/details/105887685
        //        java进程和文件管理的OS系统属于平行的两个系统，两个系统交流需要调用java下的io包来作为桥梁
        //        读取文件需要用到java.io这个和文件读取有关的包，读取文本文件需要用到如下各类：
//
//        FileInputStream（节点类）：直接操作文件的类，将数据从文件读取到java中
//        InputStreamReader（转换类）：字节到字符间转换的类，将读取进来的二进制数据转换成java能理解的字符
//        BufferedReader（修饰类）：缓存数据流，提高读取效率

        FileOutputStream fos=null;   //节点类
        OutputStreamWriter osw=null;  //字符类
        BufferedWriter bw=null;   //装饰类

        FileInputStream fis=null;  //节点类
        InputStreamReader isr=null; //转换类
        BufferedReader br=null;  //装饰类

        try {
            // 获取当前类 工作目录
            String workingDIRPath = currentWorkdingDir();

            // 文本文件 工作目录/test.txt
            String textFilePath = workingDIRPath + File.separator + "test.txt";
            System.out.println("textFilePath: "+ textFilePath);

            // 文件流 文本 写入
            fos=new FileOutputStream(textFilePath);//传入文件所在路径
            osw=new OutputStreamWriter(fos,"UTF-8"); //将fos作为参数传入osw，并且设置字符编码
            bw=new BufferedWriter(osw);
            bw.write(poem);
            bw.close();

            // 文本 写入
//            FileWriter fw = new FileWriter(textFilePath, false);
//            fw.write(poem);
//            fw.flush();
//            fw.close();
//            System.out.println("text wirite done: "+ textFilePath);

//            // 文本读取
//            FileReader fr = new FileReader(textFilePath);
//            BufferedReader br = new BufferedReader(fr);

            String line;

            // 文件流 文本 写入
            fis= new FileInputStream(textFilePath);  //填写读取文件所需要的路径
            isr= new InputStreamReader(fis,"UTF-8");  //传入fis 并且设置字符编码 ，需要与写入文件时候的编码相同
            br=new BufferedReader(isr);

            int loop = 0;
            // 按行读取数据
            while ((line = br.readLine()) != null) {
                System.out.println(loop + " : " + line);
                ++loop;
            }
            br.close();

        } catch (IOException e) {
            e.printStackTrace();
            return;
        }
        finally{

            try {
                bw.close();
                br.close();  //涉及到IO流操作的时候一定要记得关闭文件，关闭最外层流即可关掉所有的流

            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

    protected static void writePersonIntoStream(DemoPerson person, DataOutputStream dos) {
        try {
            // 写入 p1.gender 和 age
            dos.writeChar(person.gender);
            dos.writeInt(person.age);
            // 写入 p1.name 长度
            byte[] nameBytes = person.name.getBytes(StandardCharsets.UTF_8);
            dos.writeInt(nameBytes.length);
            // 写入 p1.name 字节数据
            dos.write(nameBytes);
            // 写入 p1.addr 长度
            byte[] addrBytes = person.addr.getBytes(StandardCharsets.UTF_8);
            dos.writeInt(addrBytes.length);
            // 写入 p1.addr 字节数据
            dos.write(addrBytes);

            // 已经向 输出流 dos 中 写入了多少byte数据
            System.out.println("writePersonIntoStream end, dos.size = "+dos.size());

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    protected static DemoPerson readPersonFromStream(DataInputStream dis) {
        DemoPerson persion = null;
        try {
            persion = new DemoPerson();
            persion.gender = dis.readChar();
            persion.age = dis.readInt();

            int nameByteLen = dis.readInt();
            byte[] nameBytes = new byte[nameByteLen];
            dis.read(nameBytes);
            persion.name = new String(nameBytes, StandardCharsets.UTF_8);

            int addrByteLen = dis.readInt();
            byte[] addrBytes = new byte[addrByteLen];
            dis.read(addrBytes);
            persion.addr = new String(addrBytes, StandardCharsets.UTF_8);

            //输入流 dis 中 还剩下多少byte数据
            System.out.println("readPersonFromStream end, dis.available = "+dis.available());

        } catch (IOException e) {
            e.printStackTrace();
            persion = null;
        }

        return persion;
    }

    protected static void binFileIO() {

        DemoPerson p1 = new DemoPerson("1LiLei", 'M', 12, "505_class-1-1");
        DemoPerson p2 = new DemoPerson("2HanMeiMei", 'F', 13, "504_class-1-2");
        DemoPerson p3 = new DemoPerson("3JimGreen", 'M', 11, "503_class-1-3");

        FileInputStream fis = null;
        FileOutputStream fos = null;
        DataInputStream dis = null;
        DataOutputStream dos = null;

        try {
            // 获取当前类 工作目录
            String workingDIRPath = currentWorkdingDir();

            // 文本文件 工作目录/test.bin
            String binFilePath = workingDIRPath + File.separator + "test.bin";
            System.out.println("binFilePath: " + binFilePath);

            //创建输出流
            fos = new FileOutputStream(binFilePath);
            dos = new DataOutputStream(fos);

            // 一共写入3个DemoPerson实例数据
            dos.writeInt(3);
            writePersonIntoStream(p1, dos);
            writePersonIntoStream(p2, dos);
            writePersonIntoStream(p3, dos);
            dos.close();

            //创建输入流
            fis = new FileInputStream(binFilePath);
            dis = new DataInputStream(fis);
            int objNum = dis.readInt();
            for (int i=0; i<objNum; ++i) {
                DemoPerson personFromFile = readPersonFromStream(dis);
                System.out.println("personFromFile: " + i + ", " + personFromFile);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fis != null) {
                    fis.close();
                }
                if (fos != null) {
                    fos.close();
                }
                if (dis != null) {
                    dis.close();
                }
                if (dos != null) {
                    dos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }


    }


    protected static void serializationMake() {
        // https://blog.csdn.net/qq_62414755/article/details/125886742
        //方法一：若类实现了serializable接口，则可以通过objectOutputstream和objectinputstream默认的序列化和反序列化方式，对非transient的实例变量进行序列化和反序列化。
        DemoStudent s1 = new DemoStudent("1LiLei", 'M', 8, "chunqiu-1-1", "WenLan primary NO1", "grade-2", "2(6)");

        ObjectOutputStream out1 = null;
        try {
            // 获取当前类 工作目录
            String workingDIRPath = currentWorkdingDir();

            // 文本文件 工作目录/demoTest.s1.bin
            String s1FilePath = workingDIRPath + File.separator + "demoTest.s1.bin";
            System.out.println("s1FilePath: " + s1FilePath);

            out1 = new ObjectOutputStream(new FileOutputStream(s1FilePath));
            out1.writeObject(s1);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (out1 != null) {
                    out1.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        //方法二：若类实现了serializable接口，并且定义了writeObject(objectOutputStream out)和 readObject(objectinputStream in)方法，则可以直接调用类的两种方法进行序列化和反序列化。
        DemoStudent s2 = new DemoStudent("2HanMeiMei", 'F', 9, "shangtang-1-2", "WenLan primary NO1", "grade-3", "3(6)");
        ObjectOutputStream out2 = null;
        try {
            // 获取当前类 工作目录
            String workingDIRPath = currentWorkdingDir();

            // 文本文件 工作目录/demoTest.s1.bin
            String s2FilePath = workingDIRPath + File.separator + "demoTest.s2.bin";
            System.out.println("s2FilePath: " + s2FilePath);

            out2 = new ObjectOutputStream(new FileOutputStream(s2FilePath));
            s2.writeObject(out2);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (out2 != null) {
                    out2.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        //方法三：若类实现了Externalizable接口，则必须实现readExternal(Objectinput in)和writeExternal(Objectoutput out)方法进行序列化和反序列化。
        DemoTeacher t1 = new DemoTeacher("Sheng", 'F', 29, "shangtang-2-2", "WenLan primary NO1", "senior yuwen");
        ObjectOutputStream out3 = null;
        try {
            // 获取当前类 工作目录
            String workingDIRPath = currentWorkdingDir();

            // 文本文件 工作目录/demoTest.s1.bin
            String t1FilePath = workingDIRPath + File.separator + "demoTest.t1.bin";
            System.out.println("t1FilePath: " + t1FilePath);

            out3 = new ObjectOutputStream(new FileOutputStream(t1FilePath));

            //调用 ObjectOutputStream 中的 writeObject() 方法 写对象, 会自动执行重写的writeExternal()方法
            out3.writeObject(t1);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (out3 != null) {
                    out3.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    protected static void deserializationMake() {

        // https://blog.csdn.net/qq_62414755/article/details/125886742
        //方法一：若类实现了serializable接口，则可以通过objectOutputstream和objectinputstream默认的序列化和反序列化方式，对非transient的实例变量进行序列化和反序列化。
        ObjectInputStream input1 = null;
        try {
            // 获取当前类 工作目录
            String workingDIRPath = currentWorkdingDir();

            // 文本文件 工作目录/demoTest.s1.bin
            String s1FilePath = workingDIRPath + File.separator + "demoTest.s1.bin";
            System.out.println("read from s1FilePath: " + s1FilePath);

            input1 = new ObjectInputStream(new FileInputStream(s1FilePath));
            DemoStudent s1 = (DemoStudent)input1.readObject();
            System.out.println("DemoStudent from s1FilePath: " + s1);

        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                if (input1 != null) {
                    input1.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        //方法二：若类实现了serializable接口，并且定义了writeObject(objectOutputStream out)和 readObject(objectinputStream in)方法，则可以直接调用类的两种方法进行序列化和反序列化。
        ObjectInputStream input2 = null;
        try {
            // 获取当前类 工作目录
            String workingDIRPath = currentWorkdingDir();

            // 文本文件 工作目录/demoTest.s1.bin
            String s2FilePath = workingDIRPath + File.separator + "demoTest.s2.bin";
            System.out.println("read from s2FilePath: " + s2FilePath);

            input2 = new ObjectInputStream(new FileInputStream(s2FilePath));

            DemoStudent s2 = new DemoStudent();
            s2.readObject(input2);
            System.out.println("DemoStudent readObject s2FilePath: " + s2);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (input2 != null) {
                    input2.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        //方法三：若类实现了Externalizable接口，则必须实现readExternal(Objectinput in)和writeExternal(Objectoutput out)方法进行序列化和反序列化。
        ObjectInputStream input3 = null;
        try {
            // 获取当前类 工作目录
            String workingDIRPath = currentWorkdingDir();

            // 文本文件 工作目录/demoTest.t1.bin
            String t1FilePath = workingDIRPath + File.separator + "demoTest.t1.bin";
            System.out.println("read from t1FilePath: " + t1FilePath);

            input3 = new ObjectInputStream(new FileInputStream(t1FilePath));

            //调用 ObjectInputStream 中的 readObject() 方法 写对象, 会自动执行readExternal()方法
            DemoTeacher t1 = (DemoTeacher)input3.readObject();

            System.out.println("DemoTeacher readObject t1FilePath: " + t1);

        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                if (input3 != null) {
                    input3.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    protected static void demoLogic() {
        // 文件目录遍历
        DateTimeDemo.logTs("fileSysLoop");
        fileSysLoop();

        // 文件文本读写
        DateTimeDemo.logTs("textFileIO");
        textFileIO();

        // 二进制文件读写
        DateTimeDemo.logTs("binFileIO");
        binFileIO();

        // 对象序列化写入文件
        DateTimeDemo.logTs("serializationMake");
        serializationMake();

        // 读文件反序列化对象
        DateTimeDemo.logTs("deserializationMake");
        deserializationMake();
    }
}
